package com.mlog.njyjy.ndop.share.zabbix.client;

import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mlog.njyjy.ndop.share.zabbix.ex.ZabbixClientException;
import com.mlog.njyjy.ndop.share.zabbix.request.core.ZabbixRequest;
import com.mlog.njyjy.ndop.share.zabbix.response.ZabbixResponse;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * default client of {@link ZabbixClient}
 *
 * @author alex
 * @version v1.0 2020/6/29
 */
public class DefaultZabbixClient implements ZabbixClient {

    private OkHttpClient httpClient;

    private final Logger log = LoggerFactory.getLogger(DefaultZabbixClient.class);

    private Gson gson = new GsonBuilder().create();

    /**
     * zabbix api server
     */
    private String serverUri;

    /**
     * show request detail
     */
    private boolean showRequestDetail;


    /**
     * execute
     *
     * @param request
     * @return
     * @throws ZabbixClientException
     */
    @Override
    public <T extends ZabbixResponse> T execute(ZabbixRequest<T> request) throws ZabbixClientException {
        Request httpPost = prepareRequest(request);
        try (Response response = httpClient.newCall(httpPost).execute()) {
            return deserializeResponse(response, request);
        } catch (IOException e) {
            throw new ZabbixClientException(e);
        }
    }

    /**
     * execute async
     *
     * @param request
     * @param responseHandler
     * @return
     */
    @Override
    public <T extends ZabbixResponse> void executeAsync(ZabbixRequest<T> request, ZabbixResponseHandler<? super T> responseHandler) {
        Request httpRequest = prepareRequest(request);
        Call call = httpClient.newCall(httpRequest);
        call.enqueue(new DefaultCallback<>(request, responseHandler));
    }

    /**
     * api version
     *
     * @return
     */
    @Override
    public String apiVersion() {
        return "4.0";
    }


    /**
     * 组织 params method id 等参数
     *
     * @param request
     * @return
     */
    private Request prepareRequest(ZabbixRequest request) {
        ImmutableMap<String, Object> postParam = ImmutableMap.of("jsonrpc", request.getJsonrpc(),
                "method", request.getMethod(), "id", request.getId(),
                "params", request.getParams()
        );
        if (showRequestDetail) {
            log.info("[zabbix client] request url: {} \n -d: {}", serverUri, gson.toJson(postParam));
        }
        MediaType mediaType = MediaType.parse("application/json");
        return new Request.Builder()
                .url(serverUri)
                .method("POST", RequestBody.create(mediaType, gson.toJson(postParam)))
                .addHeader("Content-Type", "application/json")
                .build();
    }

    /**
     * deserialize response
     *
     * @param response
     * @param clientRequest
     * @param <T>
     * @return
     * @throws IOException
     */
    private <T extends ZabbixResponse> T deserializeResponse(Response response,
                                                             ZabbixRequest<T> clientRequest) throws IOException {
        return clientRequest.createNewResponse(
                response.body() == null ? null : response.body().string(),
                response.code(),
                response.message(),
                gson
        );
    }

    public DefaultZabbixClient setHttpClient(OkHttpClient httpClient) {
        this.httpClient = httpClient;
        return this;
    }

    public String getServerUri() {
        return serverUri;
    }

    public DefaultZabbixClient setServerUri(String serverUri) {
        this.serverUri = serverUri;
        return this;
    }

    public DefaultZabbixClient setShowRequestDetail(boolean showRequestDetail) {
        this.showRequestDetail = showRequestDetail;
        return this;
    }

    @Override
    public void close() throws Exception {
        //

    }

    /**
     * default callback
     *
     * @param <T>
     */
    protected class DefaultCallback<T extends ZabbixResponse> implements Callback {

        private final ZabbixRequest<T> clientRequest;

        private final ZabbixResponseHandler<? super T> responseHandler;

        private final Logger log = LoggerFactory.getLogger(DefaultCallback.class);

        public DefaultCallback(ZabbixRequest<T> clientRequest,
                               ZabbixResponseHandler<? super T> responseHandler) {
            this.clientRequest = clientRequest;
            this.responseHandler = responseHandler;
        }

        @Override
        public void onFailure(Call call, IOException e) {
            log.error("Exception occurred during async execution.{}", e.getLocalizedMessage());
            responseHandler.failed(new ZabbixClientException(call.request().url() + "\n" + e.getLocalizedMessage()));
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            T result = null;
            try {
                result = deserializeResponse(response, clientRequest);
            } catch (Exception e) {
                onFailure(call, new IOException(e));
            } catch (Throwable t) {
                onFailure(call, new IOException("Exception during async request processing", t));
            }
            if (result != null) {
                responseHandler.completed(result);
            }
        }
    }
}
